package com.theory.builder;

import com.theory.config.Configuration;
import com.theory.enums.SqlLabelEnum;
import com.theory.mapping.MapperStatement;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;

public class XmlMapperBuilder extends BaseConfigBuilder{

    private Element root;
    private String resource;
    private String currentNamespace;

    public XmlMapperBuilder(InputStream inputStream, Configuration configuration, String resource) {
        super(configuration);
        this.resource = resource;
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(inputStream);
            root = document.getRootElement();
        } catch (DocumentException e) {
            throw new RuntimeException("dom4j read the xml error, Cause: " + e);
        }
    }

    public void parse(){
        parseMapper(root);
    }

    private void parseMapper(Element mapper){
        String namespace = mapper.attributeValue("namespace");
        if(namespace == null || namespace.isEmpty()){
            throw new RuntimeException(resource + " namespace can not be empty");
        }
        namespace = namespace.trim();
        currentNamespace = namespace;
        bindMapperForNamespace(namespace);

        // loop the subElements
        for (Object o : mapper.elements()) {
            Element element = (Element)o;
            if(SqlLabelEnum.isSqlLabel(element.getName())){
                // select | insert | update | delete
                parseStatement(element);
            }
            else{
                //TODO other labels
            }
        }
    }


    private void parseStatement(Element statementNode){
        if(!SqlLabelEnum.isSqlLabel(statementNode.getName()))
            throw new RuntimeException("the statementNode's label must be 'insert | update | delete | select'");

        //parse the xml
        String id = statementNode.attributeValue("id");
        String resultType = statementNode.attributeValue("resultType");
        Class<?> resultClass = resolveClass(resultType);
        String sql = statementNode.getText();
        //TODO resultMap...

        //build the MapperStatement
        MapperStatement mapperStatement = MapperStatement.Builder.aMapperStatement()
                .setId(getCompleteId(id))
                .setResultType(resultClass)
                .setSql(sql)
                .setSqlLabelEnum(SqlLabelEnum.valueOf(statementNode.getName().toUpperCase()))
                .build();

        //add the mapperStatement into the configuration
        configuration.addMapperStatement(mapperStatement);

    }

    private String getCompleteId(String id){
        if(id == null)
            return null;
        if(id.startsWith(currentNamespace + "."))
            return id;
        else{
            if(id.contains("."))
                throw new RuntimeException("dots are not allowed in element id!");
            return currentNamespace + "." + id;
        }
    }


    /**
     * bind the mapperInterface and the mapper.xml
     */
    private void bindMapperForNamespace(String namespace){
        Class<?> boundType;
        try {
            boundType = Class.forName(namespace);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(namespace + " is not found as class");
        }
        if(!configuration.hasMapper(boundType)){
            //register the mapperInterface
            configuration.registerMapper(boundType);
            //add the namespace into the loadedNamespaces
            configuration.addNamespace(namespace);
        }
    }
}
